home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PCGUIA 127
/
PC Guia 127.iso
/
Software
/
Utils
/
NEW - Stylish
/
Bin
/
stylish-0.2.1-fx+fl+tb.xpi
/
chrome
/
stylish.jar
/
content
/
stylish.js
< prev
next >
Wrap
Text File
|
2006-01-24
|
19KB
|
555 lines
/*
This code is licensed under the GPL (http://www.gnu.org/copyleft/gpl.txt)
Author: Jason Barnabe <jason_barnabe@fastmail.fm>
Release date: Oct 9, 2005
*/
function Stylish() {
this.currentURI = null;
this.CSSXULNS = "@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);";
this.CSSHTMLNS = "@namespace url(http://www.w3.org/1999/xhtml);";
this.documentRulePrefix = "@-moz-document ";
this.manageInit = function() {
var tree = document.getElementById("styles");
tree.datasources = stylishCommon.datasourceURI;
}
//fill the values
this.editInit = function() {
var o = window.arguments[0];
if ("uri" in o) {
this.currentURI = o.uri;
var node = stylishCommon.ds.getNode(o.uri);
document.getElementById("description").value = node.getTarget(stylishCommon.descriptionURI).getValue();
document.getElementById("enabled").checked = (node.getTarget(stylishCommon.enabledURI).getValue() == "true");
document.getElementById("code").value = node.getTarget(stylishCommon.codeURI).getValue();
}
if ("code" in o) {
document.getElementById("code").value = o.code;
}
}
//returns an array of the uris of the selected styles
this.getSelectedStyles = function() {
var uris = [];
var tree = document.getElementById("styles");
var rangeCount = tree.view.selection.getRangeCount();
for (var i = 0; i < rangeCount; i++) {
var start = {};
var end = {};
tree.view.selection.getRangeAt(i,start,end);
for (var c = start.value; c <= end.value; c++) {
uris.push(tree.view.getItemAtIndex(c).id);
//dont-build-content version
//uris.push(tree.view.getResourceAtIndex(c).Value);
}
}
return uris;
}
//Returns an array of objects representing the selection ranges
this.getSelectionRanges = function() {
var ranges = [];
var tree = document.getElementById("styles");
var rangeCount = tree.view.selection.getRangeCount();
for (var i = 0; i < rangeCount; i++) {
var start = {};
var end = {};
tree.view.selection.getRangeAt(i,start,end);
ranges[ranges.length] = {start: start, end: end};
}
return ranges;
}
this.setSelectionRanges = function(ranges) {
var tree = document.getElementById("styles");
for (var i = 0; i < ranges.length; i++) {
tree.view.selection.rangedSelect(ranges[i].start.value, ranges[i].end.value, true);
}
}
this.getTopVisibleRow = function() {
return document.getElementById("styles").treeBoxObject.getFirstVisibleRow();
}
this.setTopVisibleRow = function(topVisibleRow) {
return document.getElementById("styles").treeBoxObject.scrollToRow(topVisibleRow);
}
this.handleEnableButtonClick = function() {
//we want to be in the same place after the refresh
var ranges = this.getSelectionRanges();
var topVisibleRow = this.getTopVisibleRow();
this.toggleDisable();
this.setSelectionRanges(ranges);
this.setTopVisibleRow(topVisibleRow);
}
this.toggleDisable = function() {
var styles = this.getSelectedStyles();
for (var i = 0; i < styles.length; i++) {
var node = stylishCommon.ds.getNode(styles[i]);
stylishCommon.toggleNodeDisable(node, false);
}
stylishCommon.ds.save();
stylishCommon.ds.refresh(true);
}
//open the add dialog
this.openAdd = function() {
window.openDialog("chrome://stylish/content/stylish-edit.xul", "stylishEdit", "chrome,resizable", {});
}
this.handleEditButtonClick = function() {
this.openEdit(this.getSelectedStyles()[0]);
}
//open the edit dialog
this.openEdit = function(uri) {
window.openDialog("chrome://stylish/content/stylish-edit.xul", "stylishEdit", "chrome,resizable", {uri: uri});
}
//validate the user entries, throws an exception on invalid
this.update = function(uri, description, enabled, code, callBack) {
if (description == null || description == "") {
document.getElementById("description").focus();
throw document.getElementById("strings").getString("blankDescription");
}
if (code == null || code == "") {
document.getElementById("code").focus();
throw document.getElementById("strings").getString("blankCode");
}
//loadStylesheet will fire the save and close stuff if everything's ok
this.loadStylesheet(code);
}
this.applyStyle = function(siteRules) {
var uri = this.currentURI;
var description = document.getElementById("description").value;
var enabled = document.getElementById("enabled").checked;
var code = document.getElementById("code").value;
if (uri) {
var node = stylishCommon.ds.getNode(uri);
//remove the old code from the list of registered sheets
if (node.getTarget(stylishCommon.enabledURI).getValue() == "true") {
stylishCommon.unregisterNode(node);
}
node.modifyTarget(stylishCommon.descriptionURI, node.getTarget(stylishCommon.descriptionURI), description);
node.modifyTarget(stylishCommon.enabledURI, node.getTarget(stylishCommon.enabledURI), enabled ? "true" : "false");
node.modifyTarget(stylishCommon.codeURI, node.getTarget(stylishCommon.codeURI), code);
//remove site rules
var targets = node.getTargets(stylishCommon.siteURLURI);
while (targets.hasMoreElements()) {
var target = targets.getNext();
node.removeTarget(stylishCommon.siteURLURI, target);
}
targets = node.getTargets(stylishCommon.siteURLPrefixURI);
while (targets.hasMoreElements()) {
var target = targets.getNext();
node.removeTarget(stylishCommon.siteURLPrefixURI, target);
}
targets = node.getTargets(stylishCommon.siteDomainURI);
while (targets.hasMoreElements()) {
var target = targets.getNext();
node.removeTarget(stylishCommon.siteDomainURI, target);
}
} else {
//add the node
var container = stylishCommon.ds.getNode(stylishCommon.containerURI);
var node = stylishCommon.ds.getAnonymousNode();
node.addTarget(stylishCommon.descriptionURI, description);
node.addTarget(stylishCommon.enabledURI, enabled ? "true" : "false");
node.addTarget(stylishCommon.codeURI, code);
//for some reason, this will throw if you delete everything then add in the same session.
container.addChild(node);
uri = stylishCommon.getNodeURI(node);
}
//add the site rules
for (var i = 0; i < siteRules.length; i++) {
switch (siteRules[i].type) {
case "url":
node.addTarget(stylishCommon.siteURLURI, siteRules[i].site);
break;
case "url-prefix":
node.addTarget(stylishCommon.siteURLPrefixURI, siteRules[i].site);
break;
case "domain":
node.addTarget(stylishCommon.siteDomainURI, siteRules[i].site);
break;
default:
alert("Unrecognized site rule type '" + siteRules[i].type + "'.");
}
}
if (enabled) {
stylishCommon.registerNode(node);
}
//save rdf file
stylishCommon.ds.save();
stylishCommon.ds.refresh(true);
window.close();
}
this.editOK = function() {
try {
this.update(this.currentURI, document.getElementById("description").value, document.getElementById("enabled").checked, document.getElementById("code").value);
} catch (ex) {
alert(ex);
}
//cancel close will be handled further down the chain
return false;
}
//delete all selected styles
this.deleteStyle = function() {
var styles = this.getSelectedStyles();
for (var i = 0; i < styles.length; i++) {
var node = stylishCommon.ds.getNode(styles[i]);
//unapply the style if it's applied
if (node.getTarget(stylishCommon.enabledURI).getValue() == "true") {
stylishCommon.unregisterNode(node);
}
//delete it from the file
stylishCommon.ds.deleteRecursive(node);
}
stylishCommon.ds.save();
stylishCommon.ds.refresh(true);
//XXX there's a bug somewhere that deleting a style will make all the styles below it not display. to work around this, refresh the tree
//remember scroll position. don't remember selection, because everything selected will have been deleted!
var topVisibleRow = this.getTopVisibleRow();
//refresh
document.getElementById("styles").datasources = "rdf:null";
stylish.manageInit();
setTimeout("stylish.setTopVisibleRow(" + topVisibleRow + ")", 100);
}
this.styleListKeyPress = function(aEvent) {
//delete
if (aEvent.keyCode == 46) {
stylish.deleteStyle();
}
}
//Handles changes in selection for the manage dialog tree.
this.changeSelection = function() {
var edit = document.getElementById("edit");
var deleteB = document.getElementById("delete");
var disable = document.getElementById("disable");
switch (this.getSelectedStyles().length) {
case 0:
edit.disabled = true;
deleteB.disabled = true;
disable.disabled = true;
break;
case 1:
edit.disabled = false;
deleteB.disabled = false;
disable.disabled = false;
break;
default:
edit.disabled = true;
deleteB.disabled = false;
disable.disabled = false;
break;
}
}
//Insert the snippet at the start of the code textbox or highlight it if it's already in there
this.insertCodeAtStart = function(snippet) {
var codeElement = document.getElementById("code")
var position = codeElement.value.indexOf(snippet);
if (position == -1) {
//insert the code
//put some line breaks in if there's already code there
if (codeElement.value.length > 0) {
codeElement.value = snippet + "\n" + codeElement.value;
} else {
codeElement.value = snippet + "\n";
}
}
//highlight it
codeElement.setSelectionRange(snippet.length + 1, snippet.length + 1);
codeElement.focus();
}
this.openSitesDialog = function() {
window.openDialog("chrome://stylish/content/stylish-specify-sites.xul", "stylishSpecifySites", "chrome,modal,resizable", this.applySpecifySite);
}
//Process the return from the specify site dialog
this.applySpecifySite = function(data) {
if (data.length == 0) {
return;
}
var selector = "";
for (var i = 0; i < data.length; i++) {
if (selector != "") {
selector += ", ";
}
selector += data[i].type + "(" + data[i].site + ")";
}
selector = "@-moz-document " + selector + " {\n";
var codeElement = document.getElementById("code");
if (codeElement.selectionStart != codeElement.selectionEnd) {
//there's a selection, so let's cram the selection inside
var selection = codeElement.value.substring(codeElement.selectionStart, codeElement.selectionEnd);
var newValue = "";
//if there's stuff before the selection, include whitespace
if (codeElement.selectionStart > 0) {
newValue = codeElement.value.substring(0, codeElement.selectionStart) + "\n";
}
newValue += selector;
var newCaretPosition = newValue.length;
newValue += selection + "\n}";
//if there's stuff after the selection, include whitespace
if (codeElement.selectionEnd < codeElement.value.length) {
newValue += "\n" + codeElement.value.substring(codeElement.selectionEnd, codeElement.value.length);
}
} else {
//there's no selection, just put it at the end
//if there's stuff in the textbox, add some whitespace
if (codeElement.value.length > 0) {
var newValue = codeElement.value + "\n" + selector;
var newCaretPosition = newValue.length;
newValue += "\n}";
} else {
var newValue = selector;
var newCaretPosition = newValue.length;
newValue += "\n}";
}
}
codeElement.value = newValue;
codeElement.setSelectionRange(newCaretPosition, newCaretPosition);
codeElement.focus();
}
this.handleStyleListClick = function(event) {
var tree = document.getElementById("styles");
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
//if the user clicked on the enabled column, toggle enabled
if (col.value && col.value.id == "enabled-label") {
//remember our position
var ranges = this.getSelectionRanges();
var topVisibleRow = this.getTopVisibleRow();
var uri = tree.view.getItemAtIndex(row.value).id;
var node = stylishCommon.ds.getNode(uri);
stylishCommon.toggleNodeDisable(node, true);
this.setSelectionRanges(ranges);
this.setTopVisibleRow(topVisibleRow);
return;
}
}
/* this.handleStyleListMouseDown = function(event) {
var tree = document.getElementById("styles");
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
//if the user moused down on the enabled column, don't select the row
if (col.value && col.value.id == "enabled-label") {
event.preventDefault();
event.stopPropagation();
return;
}
}*/
this.handleStyleListDoubleClick = function(event) {
/*
//dont-build-content version
//only do something in the main area
if (event.target.nodeName != "treechildren") {
return;
}
//XXX this isn't perfect. for example, selecting an item then double-clicking a blank spot should
//bring up an add dialog, not an edit for the selected item
var uris = this.getSelectedStyles();
switch (uris.length) {
case 0:
stylish.openAdd();
break;
case 1:
stylish.openEdit(uris[0]);
break;
default:
//ambiguous, don't do anything
}
*/
var tree = document.getElementById("styles");
var row = {}, col = {}, obj = {};
tree.treeBoxObject.getCellAt(event.clientX, event.clientY, row, col, obj);
if (row.value == -1) {
//if the user double-clicked a blank spot, they want to add
stylish.openAdd()
} else {
//if they double-clicked a row in a place other than the enabled column, they want to edit
if (!col.value || col.value.id != "enabled-label") {
stylish.openEdit(tree.view.getItemAtIndex(row.value).id);
}
}
}
this.loadStylesheet = function(css) {
//we want to check for errors
var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
var errorListener = new StylishCSSErrorListener()
//make a fake document and apply the style to it. this will throw errors and give us a stylesheet document
var doc = document.implementation.createDocument(document.xmlns, "stylish-parse", document.documentType);
var link = doc.createElementNS(stylishCommon.HTMLNS, "link");
link.rel = "stylesheet";
link.type = "text/css";
link.href = stylishCommon.codePrefix + css;
//stylesheet loading is asynchronous
consoleService.registerListener(errorListener);
var loadedListener = new StylishStylesheetLoadedListener(doc, this.stylesheetLoaded, errorListener);
//this actually loads the sheet
doc.documentElement.appendChild(link);
//now start checking for completion
loadedListener.checkStyleLoaded();
}
this.getDocRules = function(stylesheet) {
//get an array of document rules
var docRules = [];
for (var i = 0; i < stylesheet.cssRules.length; i++) {
var rule = stylesheet.cssRules[i];
var isDocRule;
try {
rule.QueryInterface(Components.interfaces.nsIDOMCSSMozDocumentRule);
isDocRule = true;
} catch (ex) {
if (ex.name == "NS_NOINTERFACE") {
isDocRule = false;
} else {
throw ex;
}
}
/* if (rule.type == Components.interfaces.nsIDOMCSSRule.UNKNOWN_RULE) {
var mozDocPosition = rule.cssText.indexOf(this.documentRulePrefix);
//if this actually contains a document rule
if (mozDocPosition > -1) {*/
if (isDocRule) {
//get an array of the sites it applies to
var mozDocPosition = rule.cssText.indexOf(this.documentRulePrefix);
if (mozDocPosition == -1) {
alert("Rule QIs to moz-document but moz-document string not found.");
return docRules;
}
var mozDocEnd = rule.cssText.indexOf(" {");
var sitesString = rule.cssText.substring(mozDocPosition + this.documentRulePrefix.length, mozDocEnd - 1);
//this could fail if a url contains ", " in it. probably won't happen
var sites = sitesString.split(", ");
for (var j = 0; j < sites.length; j++) {
var openParenthesis = sites[j].indexOf("(\"");
var type = sites[j].substring(0, openParenthesis);
//open parenthesis + the size of (". length - 1 for zero based - 1 for the closing parenthesis
var site = sites[j].substring(openParenthesis + 2, sites[j].length - 2);
docRules[docRules.length] = {type: type, site: site};
}
}
}
return docRules;
}
this.stylesheetLoaded = function(success, data) {
//did the stylesheet load fail?
if (!success) {
throw data.exception;
}
//did the stylesheet load succeed, but the stylesheet contains errors?
if (data.errors.length > 0) {
//make a string containing a maximum number of errors
var errorString = null;
for (var i = 0; i < Math.min(data.errors.length, 3); i++) {
if (errorString == null) {
errorString = data.errors[i].message;
} else {
errorString += "\n\n" + data.errors[i].message;
}
}
//does the user want to keep going?
if (!confirm(document.getElementById("strings").getFormattedString("invalidCode", [errorString]))) {
return;
}
}
var docRules = stylish.getDocRules(data.stylesheet);
stylish.applyStyle(docRules);
}
}
function StylishCSSErrorListener() {
this.errors = [];
this.QueryInterface = function(aIID) {
if (aIID.equals(Components.interfaces.nsIConsoleListener) ||
aIID.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
}
this.observe = function(message) {
this.errors[this.errors.length] = message;
}
}
function StylishStylesheetLoadedListener(doc, callBack, errorListener) {
stylish.loadedListener = this;
this.doc = doc;
this.callBack = callBack;
this.errorListener = errorListener;
this.checkStyleLoaded = function() {
//any errors in here will seriously bork us. make sure to at least tell the user
try {
try {
var stylesheet = stylish.loadedListener.doc.QueryInterface(Components.interfaces.nsIDOMDocumentStyle).styleSheets[0];
//this'll throw if it's not done loading
stylesheet.cssRules.length;
} catch (ex) {
if (ex.name == "NS_ERROR_DOM_INVALID_ACCESS_ERR") {
//try again
setTimeout(stylish.loadedListener.checkStyleLoaded, 100);
} else {
//some other error happened
stylish.loadedListener.unregisterErrorListener();
var data = {exception: ex, stylesheet: stylesheet, errors: stylish.loadedListener.errorListener.errors};
stylish.loadedListener.callBack(false, data);
stylish.loadedListener.destroy();
}
return;
}
stylish.loadedListener.unregisterErrorListener();
stylish.loadedListener.callBack(true, {stylesheet: stylesheet, errors: stylish.loadedListener.errorListener.errors});
stylish.loadedListener.destroy();
} catch (ex) {
alert(ex);
}
}
this.unregisterErrorListener = function() {
var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
consoleService.unregisterListener(stylish.loadedListener.errorListener);
}
this.destroy = function() {
stylish.loadedListener = null;
}
}
var stylish = new Stylish();